package org.dromara.web.controller

import cn.dev33.satoken.annotation.SaIgnore
import cn.hutool.core.util.IdUtil
import cn.hutool.core.util.RandomUtil
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.validation.constraints.NotBlank
import org.dromara.common.core.constant.Constants
import org.dromara.common.core.constant.GlobalConstants
import org.dromara.common.core.domain.R
import org.dromara.common.core.domain.R.Companion.fail
import org.dromara.common.core.exception.ServiceException
import org.dromara.common.core.utils.SpringUtils
import org.dromara.common.core.utils.StringUtils
import org.dromara.common.core.utils.reflect.ReflectUtils
import org.dromara.common.mail.config.properties.MailProperties
import org.dromara.common.mail.utils.MailUtils
import org.dromara.common.ratelimiter.annotation.RateLimiter
import org.dromara.common.ratelimiter.enums.LimitType
import org.dromara.common.redis.utils.RedisUtils
import org.dromara.common.web.config.properties.CaptchaProperties
import org.dromara.common.web.enums.CaptchaType
import org.dromara.sms4j.core.factory.SmsFactory
import org.dromara.web.domain.vo.CaptchaVo
import org.springframework.expression.ExpressionParser
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import java.time.Duration

/**
 * 验证码操作处理
 *
 *@author LikeYouDo
 *@date 2025/1/3 08:43
 */
@SaIgnore
@Validated
@RestController
class CaptchaController(
    private val captchaProperties: CaptchaProperties,
    private val mailProperties: MailProperties
) {

    companion object {
        private val log = KotlinLogging.logger {}
    }

    /**
     * 短信验证码
     *
     * @param phonenumber 用户手机号
     */
    @RateLimiter(key = "#phonenumber", time = 60, count = 1)
    @GetMapping("/resource/sms/code")
    fun smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") phonenumber: String): R<Void?> {
        val key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber
        val code = RandomUtil.randomNumbers(4)
        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION))
        // 验证码模板id 自行处理 (查数据库或写死均可)
        val templateId = ""
        val map = LinkedHashMap<String, String>(1)
        map["code"] = code
        val smsBlend = SmsFactory.getSmsBlend("config1")
        val smsResponse = smsBlend.sendMessage(phonenumber, templateId, map)
        if (!smsResponse.isSuccess) {
            log.error { "验证码短信发送异常 => $smsResponse" }
            return R.fail(smsResponse.data.toString())
        }
        return R.ok()
    }

    /**
     * 邮箱验证码
     *
     * @param email 邮箱
     */
    @GetMapping("/resource/email/code")
    fun emailCode(email: @NotBlank(message = "{user.email.not.blank}") String): R<Void?> {
        if (!mailProperties.enabled) {
            return fail<Void?>("当前系统没有开启邮箱功能！")
        }
        SpringUtils.getAopProxy(this).emailCodeImpl(email)
        return R.ok()
    }


    /**
     * 邮箱验证码
     * 独立方法避免验证码关闭之后仍然走限流
     */
    @RateLimiter(key = "#email", time = 60, count = 1)
    fun emailCodeImpl(email: @NotBlank(message = "{user.email.not.blank}") String) {
        val key = GlobalConstants.CAPTCHA_CODE_KEY + email
        val code = RandomUtil.randomNumbers(4)
        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION))
        try {
            MailUtils.sendText(
                email,
                "登录验证码",
                "您本次验证码为：" + code + "，有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟，请尽快填写。"
            )
        } catch (e: Exception) {
            log.error { "验证码短信发送异常 => $e.message" }
            throw ServiceException(e.message)
        }
    }

    /**
     * 生成验证码
     */
    @GetMapping("/auth/code")
    fun getCode(): R<CaptchaVo?> {
        val captchaEnabled = captchaProperties.enable
        if (!captchaEnabled) {
            val captchaVo = CaptchaVo()
            captchaVo.captchaEnabled = false
            return R.ok(captchaVo)
        }
        return R.ok(SpringUtils.getAopProxy(this).getCodeImpl())
    }

    /**
     * 生成验证码
     */
    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
    fun getCodeImpl(): CaptchaVo {
        // 保存验证码信息
        val uuid = IdUtil.simpleUUID()
        val verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid
        // 生成验证码
        val captchaType = captchaProperties.type
        val codeGenerator = if (CaptchaType.MATH == captchaType) {
            ReflectUtils.newInstance(captchaType.clazz, captchaProperties.numberLength, false)
        } else {
            ReflectUtils.newInstance(captchaType.clazz, captchaProperties.charLength)
        }
        val captcha = SpringUtils.getBean(captchaProperties.category.clazz)
        captcha.generator = codeGenerator
        captcha.createCode()
        // 如果是数学验证码，使用SpEL表达式处理验证码结果
        var code = captcha.code
        if (CaptchaType.MATH == captchaType) {
            val parser: ExpressionParser = SpelExpressionParser()
            val exp = parser.parseExpression(StringUtils.remove(code, "="))
            code = exp.getValue(String::class.java)
        }
        RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION))
        val captchaVo = CaptchaVo()
        captchaVo.uuid = uuid
        captchaVo.img = captcha.imageBase64
        return captchaVo
    }

}
